package com.zhao.dota.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhao.dota.bo.AuthenticateUser;
import com.zhao.dota.config.WebSocketConfig;
import com.zhao.dota.security.store.TokenStore;
import com.zhao.dota.service.WechatService;
import com.zhao.dota.struct.WsMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint(value = "/ws/asset", configurator = WebSocketConfig.class)
@Component
@Slf4j
public class WebSocketServer {

    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private WechatService wechatService;
    @Resource
    private TokenStore tokenStore;

    private static final WsMessage<Void> PONG = new WsMessage<>();

    private static final Map<String, Method> METHOD_MAP = new HashMap<>();

    private static final Map<String, AuthenticateUser> SESSION_ID_AUTHENTICATE_USER_MAP = new ConcurrentHashMap<>();
    // 暂时不支持多点登陆
    private static final Map<Integer, List<Session>> USER_ID_SESSION_MAP = new ConcurrentHashMap<>();

    static {
        PONG.setAction("pong");

        Class<? extends WebSocketServer> aClass = WebSocketServer.class;
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method : methods) {
            METHOD_MAP.put(method.getName(), method);
        }
    }

    // concurrent包的线程安全Set，用来存放每个客户端对应的Session对象。
    private static final ConcurrentHashMap<String, Session> SESSION_MAP = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        SESSION_MAP.put(session.getId(), session);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        SESSION_MAP.remove(session.getId());
        AuthenticateUser authenticateUser = SESSION_ID_AUTHENTICATE_USER_MAP.remove(session.getId());
        if (authenticateUser != null) {
            USER_ID_SESSION_MAP.remove(authenticateUser.getId());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        if (log.isTraceEnabled()) {
            log.trace("来自客户端的消息：{}", message);
        }
        try {
            WsMessage wsMessage = objectMapper.readValue(message, WsMessage.class);
            if (!StringUtils.hasText(wsMessage.getSessionId())) {
                wsMessage.setSessionId(session.getId());
            }
            Method method = METHOD_MAP.get(wsMessage.getAction());
            if (method != null) {
                Parameter[] parameters = method.getParameters();
                Object[] params = new Object[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if (parameter.getType() == WsMessage.class) {
                        params[i] = wsMessage;
                    }
                    if (parameter.getType() == String.class) {
                        params[i] = message;
                    }
                    if (parameter.getType() == Session.class) {
                        params[i] = session;
                    }
                }
                method.invoke(this, params);
            }
        } catch (JsonProcessingException | InvocationTargetException | IllegalAccessException e) {
            log.error("ws异常， messge:" + message, e);
        }
    }

    public void ping(WsMessage wsMessage) {
        sendMessage(wsMessage.getSessionId(), PONG);
    }

    public void getId(Session session) {
        WsMessage<Void> wsMessage = new WsMessage<>();
        wsMessage.setAction("getId");
        sendMessage(session, wsMessage);
    }

    public void setUser(WsMessage wsMessage, Session session) {
        AuthenticateUser authenticateUser = tokenStore.readAuthentication(String.valueOf(wsMessage.getData()));
        if (authenticateUser != null) {
            SESSION_ID_AUTHENTICATE_USER_MAP.put(wsMessage.getSessionId(), authenticateUser);
            List<Session> sessions = USER_ID_SESSION_MAP.get(authenticateUser.getId());
            if (sessions == null) {
                sessions = new ArrayList<>();
            }
            sessions.add(session);
            USER_ID_SESSION_MAP.put(authenticateUser.getId(), sessions);
        }
    }

    /**
     * 出现错误
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error(String.format("发生错误：%s，Session ID： %s", error.getMessage(), session.getId()), error);
    }

    /**
     * 发送消息，实践表明，每次浏览器刷新，session会发生变化。
     *
     * @param session
     * @param message
     */
    public <T> void sendMessage(Session session, T message) {
        WsMessage<T> wsMessage = new WsMessage<>();
        wsMessage.setData(message);
        sendMessage(session, wsMessage);
    }

    public <T> void sendMessage(Session session, WsMessage<T> wsMessage) {
        try {
            wsMessage.setSessionId(session.getId());
            session.getBasicRemote().sendText(objectMapper.writeValueAsString(wsMessage));
        } catch (IOException e) {
            log.error("发送消息出错：" + e.getMessage(), e);
        }
    }

    /**
     * 群发消息
     *
     * @param message
     */
    public void broadCastInfo(String message) {
        for (Session session : SESSION_MAP.values()) {
            if (session.isOpen()) {
                sendMessage(session, message);
            }
        }
    }

    /**
     * 指定Session发送消息
     *
     * @param sessionId
     * @param message
     */
    public <T> void sendMessage(String sessionId, T message) {
        Session session = SESSION_MAP.get(sessionId);
        if (session != null) {
            sendMessage(session, message);
        } else {
            log.warn("没有找到你指定ID的会话：{}", sessionId);
        }
    }

    public <T> void sendMessage(String sessionId, WsMessage<T> wsMessage) {
        Session session = SESSION_MAP.get(sessionId);
        if (session != null) {
            sendMessage(session, wsMessage);
        } else {
            log.warn("没有找到你指定ID的会话：{}", sessionId);
        }
    }

    public <T> void sendUserMessage(Integer userId, WsMessage<T> wsMessage) {
        List<Session> sessions = USER_ID_SESSION_MAP.get(userId);
        if (sessions != null) {
            for (Session session : sessions) {
                sendMessage(session, wsMessage);
            }
        }
    }

}
